import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.*;
import java.awt.event.*;

public class OdcaApplet
extends java.applet.Applet
implements Runnable
{
    static final long serialVersionUID = 0;
    
	private Image			oa_buffer;
	private Graphics		oa_buffer_graphics;
	
	private int			oa_odca_width;
	private int			oa_odca_height;
	private int			oa_buffer_height;
	private int			oa_buffer_offset;
	
	private int			oa_sleep_factor = 1;
	
	private MemoryImageSource	oa_odca_image_source;
	
	private Odca			oa_source_odca;
	
	private boolean		oa_pause = false;
	private boolean		oa_genone = false;
	private boolean		oa_twogen = false;
	
	private KeyAdapter		oa_key_handler
	= new KeyAdapter()
	{
		public synchronized void keyTyped(KeyEvent e)
		{
			int key = e.getKeyChar();
            
            System.out.println("Got key = <" + (char) key + ">");
			
			if (key == (int) 'n')
			{
				oa_source_odca.randomize();
				oa_source_odca.randomRule();
				oa_source_odca.reportRule();
				oa_pause = false;
			}
			else if (key == (int) 'm')
			{
				oa_source_odca.randomize();
				oa_source_odca.mutateRule();
				oa_source_odca.reportRule();
			}
			if (key == (int) 'r')
			{
				oa_source_odca.reportRule();
			}
			else if (key == (int) 'i')
			{
				oa_source_odca.randomize();
				oa_pause = false;
			}
			else if (key == (int) ' ')
			{
				oa_pause = !oa_pause;
			}
			else if ((key == (int) '+') || (key == (int) '='))
			{
				if (oa_sleep_factor > 0)
				{
					oa_sleep_factor >>= 1;
				}
                System.out.println("oa_sleep_factor now " + oa_sleep_factor);
			}
			else if (key == (int) '-')
			{
				if (oa_sleep_factor == 0)
				{
					oa_sleep_factor = 1;
				}
				else
				{
					oa_sleep_factor <<= 1;
				}
                System.out.println("oa_sleep_factor now " + oa_sleep_factor);
			}
			else if (key == (int) '\n')
			{
				oa_genone = true;
			}
			else if (key == (int) 'v')
			{
				oa_twogen = !oa_twogen;
			}
			else if ((key >= (int) '1') && (key <= (int) '3'))
			{
				setPattern(key - (int) '0');
			}
			else if (key == (int) 'z')
			{
				setPattern(0);
			}
		}
	};
	
	public void setPattern(int val)
	{
		val &= 3;
		int i;
		int cells[] = oa_source_odca.pix();
		
		for (i = 0; i < (oa_odca_width + 4); i++)
		{
			cells[i] = 0;
		}
		
		cells[oa_odca_width / 2] = val;
		cells[(oa_odca_width / 2) + 1] = val;
	}
	
	public void init()
	{
		// Initialize the sizes of the odca based on the applet size.
		oa_odca_width = getWidth();
		oa_odca_height = getHeight();
		
		// Set up the buffer size as double the odca height and initialize the
		// offset that we draw from
		oa_buffer_height = 2 * oa_odca_height;
		oa_buffer_offset = 0;
		
		// Add an event handler for keypresses.
		addKeyListener(oa_key_handler);
		
//		int rule[] = { 3, 3, 1, 2, 0, 3, 1, 3, 2, 3 };
//		int rule[] = { 0, 1, 0, 3, 3, 1, 0, 1, 2, 2 };
//		int rule[] = { 1, 2, 2, 3, 0, 1, 3, 1, 0, 3 };
//		int rule[] = { 3, 2, 2, 3, 2, 3, 0, 0, 2, 0 };
//		int rule[] = { 3, 0, 3, 2, 3, 3, 1, 1, 2, 0 };
//		int rule[] = { 0, 1, 3, 2, 3, 0, 2, 3, 0, 2 };
//		int rule[] = { 2, 1, 2, 1, 0, 0, 2, 2, 3, 3 };
//		int rule[] = { 3, 1, 1, 3, 2, 0, 0, 1, 1, 1 };
		// 0, 2, 2, 3, 2, 2, 3, 3, 0, 2,
		// 1, 2, 1, 3, 1, 3, 1, 2, 1, 3,
		// 1, 2, 1, 3, 1, 3, 1, 2, 1, 3, 
		// 3, 1, 3, 3, 3, 0, 0, 3, 2, 1,
//		int rule[] = { 3, 2, 2, 3, 1, 0, 0, 1, 0, 0 };
//		int rule[] = { 3, 0, 2, 3, 1, 0, 1, 1, 0, 0 };
//		int rule[] = { 0, 1, 1, 3, 2, 0, 3, 1, 0, 0 };
//		int rule[] = { 0, 1, 2, 0, 3, 0, 0, 1, 0, 3 };
		// 1, 0, 1, 3, 0, 0, 2, 3, 3, 2
		// 3, 1, 0, 0, 0, 3, 3, 3, 1, 2
		// 0, 2, 0, 3, 2, 2, 0, 1, 1, 1
		// 1, 2, 1, 3, 3, 0, 1, 1, 0, 1

		//int rule[] = { 0, 1, 0, 1, 2, 2, 3, 3, 0, 2 };
		//int rule[] = { 0, 2, 0, 3, 2, 2, 0, 1, 1, 1 };
		// 0, 0, 2, 3, 3, 2, 0, 2, 3, 3
		// 2, 0, 1, 1, 0, 3, 2, 3, 2, 1
		// 3, 0, 2, 0, 0, 1, 3, 2, 3, 2
		// 3, 1, 2, 0, 1, 0, 3, 3, 2, 0
		// 1, 1, 0, 1, 1, 3, 1, 1, 1, 1
		
		int rule[] = { 3, 1, 2, 0, 1, 0, 3, 3, 2, 0 };		        
		
		// Create a new one-dimensional cellular automaton, give it a transition
		// rule, and initialize its cells with random states.
		oa_source_odca = new Odca(oa_odca_width);
		oa_source_odca.setRule(rule);
		oa_source_odca.randomize();
		
		// Create an indexed color model that the odca image source can use to
		// create its images.  The four colors here represent, in order, red,
		// green, yellow, and blue.
		byte r[] = { (byte) 0xff, (byte) 0x00, (byte) 0xff, (byte) 0x00 };
		byte g[] = { (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0x00 };
		byte b[] = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff };
		
		IndexColorModel cm = new IndexColorModel(8, 4, r, g, b);
		
		// Get a copy of the array that the stores each state into.
		int pix[] = oa_source_odca.pix();
		
		// Create the image source.  This source is as wide as the odca and one
		// pixel high, thus it can be used to create images representing one
		// generation of cells from the odca.  It uses the pixel array from inside 
		// the odca, so it creates images representing the current generation of
		// cells from the odca.  The image source uses the color model we created
		// above to turn cell states into pixels.
		oa_odca_image_source = new MemoryImageSource(oa_odca_width, 1, cm,
				pix, 2, oa_odca_width);
		
		// Create the offscreen image source we use for our double-buffering.
		// This buffer is twice the height of the onscreen odca, and always
		// contains two copies of the last odca_height generations of pixels.  By
		// copying from changing offsets within this buffer, we can make the
		// onscreen image appear to scroll without having to do a copyImage
		// operation within the buffer.  In other words, it's about twice as fast
		// this way.
		oa_buffer = createImage(oa_odca_width, oa_buffer_height);
		
		// Create a graphics object we can use to draw within the offscreen
		// buffer.
		oa_buffer_graphics = oa_buffer.getGraphics();
		
		// Start the worker thread running.
		Thread engine = new Thread(this);
		engine.start();
	}
	
	public void next()
	{
		// Generation the next generation of cell states within the odca.
		oa_source_odca.generate();
		
		// Create an image representing the new generation of cells.
		Image img = createImage(oa_odca_image_source);
		
		// Draw the generation image twice within the offscreen buffer.
		oa_buffer_graphics.drawImage(img, 0, oa_buffer_offset, null);
		oa_buffer_graphics.drawImage(img, 0, oa_buffer_offset + oa_odca_height,
				null);
		
		// Increment the offset from which the repaint method copies onscreen
		// images.
		oa_buffer_offset++;
		
		// If the buffer offset has reached the image height, reset it to the top
		// of the buffer.
		if (oa_buffer_offset == oa_odca_height)
		{
			oa_buffer_offset = 0;
		}
	}
	
	public void run()
	{
		Thread me = Thread.currentThread();
		me.setPriority(Thread.NORM_PRIORITY);
		
		// Loop forever
		while(true)
		{
			try
			{
				// If generation isn't paused, then create a new generation and
				// paint it.
				if (!oa_pause | oa_genone)
				{
					synchronized(this)
					{
						// Create the next generation of cells and update the
						// offscreen buffer
						next();
						
						// If we're in double-generate mode, compute a second generation.
						if (oa_twogen)
						{
							next();
						}

						// Cause the component to be repainted ASAP
						repaint();
						
						// Wait for some amount of time to make the scrolling
						// smoother.
						Thread.sleep(oa_sleep_factor);
						
						oa_genone = false;
					}
				}
				else
				{
					// If we're paused, then wait a bit to keep from chewing up
					// CPU cycles.
					Thread.sleep(100);
				}
			}
			catch(InterruptedException e)
			{
				// We have to catch the interrupt, but there's nothing much we can 
				// do with it but ignore it.
			}
		}
	}
	
	public void paint(Graphics graphics)
	{
		// Copy the offscreen buffer to the visible applet.
		graphics.drawImage(oa_buffer, 0, -oa_buffer_offset, null);
	}
	
	public void update(Graphics graphics)
	{
		// This method is VERY important for smooth scrolling.  The usual update
		// function will clear the background before redrawing the applet.  We
		// don't want that, because it causes FLICKER.  Since we're using
		// double-buffering, there's no need to clear the applet - so we override
		// the parent class's definition of update with ours: which paints the
		// applet without first clearing it.
		paint(graphics);
	}
}
